﻿using System;
using System.Collections.Generic;

using TracedReality;
using TracedReality.Primitive;

namespace Interface.Visualization
{
    class CullingTree
    {
        BoundingBox bound;
        List<Shape> shapes;
        CullingTree left;
        CullingTree right;
        int depth;

        public bool Leaf { get { return left == null; } }
        public int Height
        {
            get
            {
                if (Leaf) { return 1; }
                return 1 + Math.Max(left.Height, right.Height);
            }
        }
        public int Count
        {
            get
            {
                if (Leaf) { return shapes.Count; }
                return shapes.Count + left.Count + right.Count;
            }
        }

        public CullingTree(BoundingBox bb, int d = 0)
        {
            bound = bb;
            shapes = new List<Shape>();
            left = null;
            right = null;
            depth = d;
        }

        public void Add(Shape s)
        {
            if (!bound.overlaps(s.bound)) { return; }
            if (Leaf) { shapes.Add(s); return; }

            if (left.bound.overlaps(s.bound) && right.bound.overlaps(s.bound)) { shapes.Add(s); return; }

            left.Add(s);
            right.Add(s);
        }

        public void Split(int maxDepth, int maxCapacity)
        {
            if (!Leaf) { left.Split(maxCapacity, maxCapacity); right.Split(maxDepth, maxCapacity); return; }
            if (depth > maxDepth) { return; }
            if (shapes.Count < maxCapacity) { return; }

            Vector diff = bound.max - bound.min;
            Vector lMin = new Vector(bound.min);
            Vector rMin = new Vector(bound.min);
            Vector lMax = new Vector(bound.max);
            Vector rMax = new Vector(bound.max);

            if (diff.x >= diff.y && diff.x >= diff.z)
            {
                diff = new Vector(diff.x / 2, 0, 0);
            }
            else if (diff.y >= diff.x && diff.y >= diff.z)
            {
                diff = new Vector(0, diff.y / 2, 0);
            }
            else
            {
                diff = new Vector(0, 0, diff.z / 2);
            }

            rMin += diff;
            lMax -= diff;

            left = new CullingTree(new BoundingBox(lMin, lMax), depth + 1);
            right = new CullingTree(new BoundingBox(rMin, rMax), depth + 1);

            List<Shape> oldList = shapes;
            shapes = new List<Shape>();
            foreach (Shape s in oldList) { Add(s); }

            left.Split(maxDepth, maxCapacity);
            right.Split(maxDepth, maxCapacity);
        }

        public List<Shape> GetAllShapes()
        {
            List<Shape> result = new List<Shape>(shapes);
            if (!Leaf)
            {
                result.AddRange(left.GetAllShapes());
                result.AddRange(right.GetAllShapes());
            }

            return result;
        }

        public List<Shape> GetShapesInFrustum(ViewFrustum frustum)
        {
            if (frustum == null) { throw new NullReferenceException("frustum can't be null"); }

            List<Shape> result = new List<Shape>();

            ViewFrustum.IntersectionResult iResult = frustum.BoundingBoxInFrustum(bound);

            // Outside means this box is not in the view frustum
            if (iResult == ViewFrustum.IntersectionResult.Outside) { return result; }
            // Inside means this box is entirely inside the view frustum
            else if (iResult == ViewFrustum.IntersectionResult.Inside) { return GetAllShapes(); }
            // Intersecting means that the box is partially inside the view frustum
            foreach (Shape s in shapes)
            {
                if (frustum.BoundingBoxInFrustum(s.bound) != ViewFrustum.IntersectionResult.Outside) { result.Add(s); }
            }

            if (!Leaf)
            {
                result.AddRange(left.GetShapesInFrustum(frustum));
                result.AddRange(right.GetShapesInFrustum(frustum));
            }

            return result;
        }
    }
}
